/*******************************************************************************
版权声明: Copyright(C) HT-Tech Co., Ltd. 2020-2021. All rights reserved.
文件名称: htssl.c
文件描述: 国密SSL创建
创 建 者: 陈位仅
创建时间: 2024年11月13日
修改历史:
1. 2024年11月13日   陈位仅      创建文件 
*******************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "htmacro.h"
#include "htcfg.h"
#include "htssl.h"

#define HTSSL_IPv4_FAMILY   1
#define HTSSL_IPv6_FAMILY   2
// 检查IP地址是IPv4还是IPv6
static int htssl_family(char *ipstr)
{
    struct sockaddr_in sa4;
    struct sockaddr_in6 sa6;

    if (inet_pton(AF_INET, ipstr, &(sa4.sin_addr)) == 1) {
        return HTSSL_IPv4_FAMILY;
    } else if (inet_pton(AF_INET6, ipstr, &(sa6.sin6_addr)) == 1) {
        return HTSSL_IPv6_FAMILY;
    }

    return 0;
}

// 建立国密SSL隧道
int htssl_create(IN ht_cfg_t *pcfg, OUT htssl_info_t *pInfo, IN char *pIP, IN short port)
{
    int fd = -1;                // 套接字文件描述符
    int family = 0;             // 网关地址类型
    struct sockaddr_in addr;    // 网关地址结构
    struct sockaddr_in6 addr6;  // 网关IPv6地址结构
    const SSL_METHOD *method;   // SSL终端算法套件
    SSL_CTX *sslCtx = NULL;     // SSL隧道上下文
    SSL *ssl = NULL;            // SSL通信结构

    // 参数校验
    if (NULL == pcfg || NULL == pInfo || NULL == pIP || 0 == port) {
        return -1;
    }
 
    // 终端工作套件
    method = NTLS_client_method();
    // 创建SSL隧道上下文
    sslCtx = SSL_CTX_new(method);
    if (sslCtx == NULL) {
        perror("Unable to create SSL context");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    // 启用国密
    SSL_CTX_enable_ntls(sslCtx);

    // 加载根证书
    if(SSL_CTX_load_verify_locations(sslCtx, pcfg->rootca, NULL) <= 0)
    {
        ERR_print_errors_fp(stderr);
        goto ERROR;
    }

    // 设备证书加载
    if (!SSL_CTX_use_sign_certificate_file(sslCtx, pcfg->signcrt, SSL_FILETYPE_PEM)
        || !SSL_CTX_use_sign_PrivateKey_file(sslCtx, pcfg->signkey, SSL_FILETYPE_PEM)
        || !SSL_CTX_use_enc_certificate_file(sslCtx, pcfg->enccrt, SSL_FILETYPE_PEM)
        || !SSL_CTX_use_enc_PrivateKey_file(sslCtx, pcfg->enckey, SSL_FILETYPE_PEM)) {
        ERR_print_errors_fp(stderr);
        goto ERROR;
    }

    // 客户端验证服务器证书
    SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER, NULL);

    // 检测IP地址字符串
    family = htssl_family(pIP);
    if (HTSSL_IPv4_FAMILY == family) {
        // 创建套接字
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
            perror("Unable to create socket");
            goto ERROR;
        }

        // 网关地址信息封装
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET, pIP, &addr.sin_addr.s_addr);

        // 连接网关
        if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
            perror("Unable to TCP connect to server");
            goto ERROR;
        }
    } else {
        // 创建套接字
        fd = socket(AF_INET6, SOCK_STREAM, 0);
        if (fd < 0) {
            perror("Unable to create socket");
            goto ERROR;
        }

        // 网关地址信息封装
        memset(&addr6, 0, sizeof(addr6));
        addr6.sin6_family = AF_INET6;
        addr6.sin6_port = htons(port);
        // 将IPv6地址字符串转换为二进制形式
        if (inet_pton(AF_INET6, pIP, &addr6.sin6_addr) <= 0) {
            printf("Invalid address/ Address not supported \n");
            goto ERROR;
        }

        // 连接网关
        if (connect(fd, (struct sockaddr *)&addr6, sizeof(addr6)) != 0) {
            perror("Unable to TCP connect to server");
            goto ERROR;
        }
    }

    // 创建SSL对象
    ssl = SSL_new(sslCtx);
    // 绑定SSL对象和套接字
    SSL_set_fd(ssl, fd);

    // SSL隧道建立
    if (SSL_connect(ssl) == 1) {
        pInfo->sslCtx = sslCtx;
        pInfo->ssl = ssl;
        pInfo->fdNet = fd;
    } else {
        printf("SSL connection to server failed\n\n");
        ERR_print_errors_fp(stderr);
        goto ERROR;
    }

    return 0;

ERROR:
    // 释放资源
    if (ssl != NULL) {
        SSL_shutdown(ssl);
        SSL_free(ssl);
    }
    SSL_CTX_free(sslCtx);
    safe_close(pInfo->fdTun, close);

    return -1;
}

// 拆除国密SSL隧道
void htssl_final(htssl_info_t *pInfo)
{
    // SSL隧道拆除
    if (pInfo->ssl) {
        SSL_shutdown(pInfo->ssl);
        SSL_free(pInfo->ssl);
    }

    // SSL隧道上下文拆除
    SSL_CTX_free(pInfo->sslCtx);

    // 套接字拆除
    safe_close(pInfo->fdTun, close);

    return;
}
